/*
Copyright (C) 2022-2024 Inspur Corp. All Rights Reserved.

SPDX-License-Identifier: Apache-2.0
*/
package service

import (
	"did-service/bean/entity"
	"did-service/bean/model"
	"did-service/bean/param"
	model2 "did-service/chain/model"
	"did-service/common/constant"
	"did-service/common/crypto/gm"
	"did-service/common/db"
	"did-service/common/utils"
	"did-service/common/uuid"
	"encoding/json"
	"strconv"
	"time"

	"github.com/pkg/errors"
)

type CptIssuer struct {
	CredentialCpt   entity.CredentialCpt   `xorm:"extends"`
	AuthorityIssuer entity.AuthorityIssuer `xorm:"extends"`
}

func (ds *DidService) CreateCpt(cptParam *param.DidCPTParam) (*model.CptResult, error) {
	generate := uuid.Generate()
	marshal, jsonErr := json.Marshal(cptParam.CptJsonSchema)
	if jsonErr != nil {
		log.Error(jsonErr)
		return &model.CptResult{}, errors.New("json转换失败!")
	}
	signature, sign, err := ds.GetSignature(cptParam, marshal)
	if err != nil {
		log.Errorf("get signatureErr error: %v", err)
		return nil, err
	}
	timeNow := time.Now()
	defaultVersion, _ := strconv.Atoi(constant.DefaultVersion)
	credentialCpt := &entity.CredentialCpt{
		Id:             generate,
		Title:          cptParam.CptJsonSchema.Title,
		Description:    cptParam.CptJsonSchema.Description,
		Type:           cptParam.CptJsonSchema.Type,
		PublisherDid:   signature.Did,
		ProofType:      cptParam.CptJsonSchema.ProofType,
		ProofSignature: sign,
		CptVersion:     defaultVersion,
		PublisherId:    signature.Id,
		CreatedTime:    timeNow,
		UpdatedTime:    timeNow,
		Status:         constant.Status0,
	}
	_, err = ds.engine.InsertOne(credentialCpt)
	if err != nil {
		log.Error(err)
		return nil, err
	}

	insertErr := ds.common(cptParam, generate)
	if insertErr != nil {
		return nil, insertErr
	}

	// 上链
	linkErr := ds.Uplink(signature, credentialCpt)
	if linkErr != nil {
		log.Errorf("get signatureErr error: %v", linkErr)
		return nil, linkErr
	}

	cptResult := &model.CptResult{
		CptId:      generate,
		CptVersion: defaultVersion,
	}
	return cptResult, nil
}

func (ds *DidService) GetCpt(cptId string) (*model.GetCptResult, error) {
	cpt := make([]entity.CredentialCpt, 0)
	cptErr := ds.engine.Where("id = ?", cptId).Find(&cpt)
	if cptErr != nil {
		return nil, cptErr
	}
	if len(cpt) == 0 {
		return nil, errors.New("CPT信息为空!")
	}
	cptClaim := make([]entity.CredentialCptClaim, 0)
	cptClaimErr := ds.engine.Where("cpt_id = ? and status = '1'", cptId).Find(&cptClaim)
	if cptClaimErr != nil {
		return nil, cptClaimErr
	}
	if len(cptClaim) == 0 {
		return nil, errors.New("CPTClaim信息为空!")
	}
	createdFormat := cpt[0].CreatedTime.Format(constant.TimeLayout)
	updateFormat := cpt[0].UpdatedTime.Format(constant.TimeLayout)

	schema := model.CptJsonSchema{}
	properties := schema.Properties
	for _, claim := range cptClaim {
		propertys := model.Propertys{}
		propertys.Type = claim.ValueType
		propertys.Description = claim.Description
		propertys.Property = claim.Property
		propertys.Enum = claim.ValueEnum
		properties = append(properties, propertys)
	}
	schema.Title = cpt[0].Title
	schema.Description = cpt[0].Description
	schema.Properties = properties
	info := model.CptBaseInfo{}
	info.CptId = cptId
	info.CptVersion = cpt[0].CptVersion
	info.Type = cpt[0].Type
	data := model.CptMetaData{}
	data.CptSignature = cpt[0].ProofSignature
	data.CptPublisher = cpt[0].PublisherDid
	data.ProofType = cpt[0].ProofType
	data.Created = createdFormat
	data.Updated = updateFormat
	cptResults := &model.GetCptResult{
		CptJsonSchema: schema,
		CptBaseInfo:   info,
		CptMetaData:   data,
	}
	return cptResults, nil
}

func (ds *DidService) EditCpt(cptParam *param.DidCPTParam) (*model.CptResult, error) {
	marshal, jsonErr := json.Marshal(cptParam.CptJsonSchema)
	if jsonErr != nil {
		log.Error(jsonErr)
		return &model.CptResult{}, errors.New("json转换失败!")
	}
	_, sign, sgErr := ds.GetSignature(cptParam, marshal)
	if sgErr != nil {
		log.Errorf("get signatureErr error: %v", sgErr)
		return nil, sgErr
	}
	var cpt entity.CredentialCpt
	ok, cptErr := ds.engine.Where("id = ?", cptParam.CptId).Get(&cpt)
	if cptErr != nil {
		return nil, cptErr
	}
	if !ok {
		return nil, errors.New("cpt id not exist")
	}
	version := cpt.CptVersion + 1

	cptClaims := make([]entity.CredentialCptClaim, 0)
	cptClaimsErr := ds.engine.Where("cpt_id = ? and status = '1'", cptParam.CptId).Find(&cptClaims)
	if cptClaimsErr != nil {
		log.Errorf("get cptClaims error: %v", cptClaimsErr)
		return &model.CptResult{}, cptClaimsErr
	}
	if len(cptClaims) == 0 {
		return &model.CptResult{}, errors.New("cptClaims信息为空!")
	}

	signature, _, signatureErr := ds.GetSignature(cptParam, marshal)
	if signatureErr != nil {
		log.Errorf("get signatureErr error: %v", signatureErr)
		return nil, signatureErr
	}

	// 上链
	linkErr := ds.Uplink(signature, &cpt)
	if linkErr != nil {
		log.Errorf("get signatureErr error: %v", linkErr)
		return nil, linkErr
	}

	credentialCpt := &entity.CredentialCpt{
		Title:          cptParam.CptJsonSchema.Title,
		Description:    cptParam.CptJsonSchema.Description,
		PublisherDid:   cptParam.Did,
		ProofSignature: sign,
		Status:         constant.StatusAudit,
		UpdatedTime:    time.Now(),
		CptVersion:     version,
	}
	_, err := ds.engine.Update(credentialCpt, &entity.CredentialCpt{Id: cptParam.CptId})
	if err != nil {
		log.Error(err)
		return nil, err
	}
	credentialCptClaim := &entity.CredentialCptClaim{
		Status: "0",
	}
	_, errs := ds.engine.Update(credentialCptClaim, &entity.CredentialCptClaim{CptId: cptParam.CptId})
	if errs != nil {
		log.Error(errs)
		return &model.CptResult{}, errs
	}
	insertErr := ds.common(cptParam, cptParam.CptId)
	if insertErr != nil {
		return nil, insertErr
	}

	cptResult := &model.CptResult{
		CptId:      cptParam.CptId,
		CptVersion: version,
	}
	return cptResult, nil
}

func (ds *DidService) common(cptParam *param.DidCPTParam, cptId string) error {
	properties := cptParam.CptJsonSchema.Properties
	for i, pro := range properties {
		var enum string
		if pro.Enum == "" {
			enum = ""
		} else {
			var data interface{}
			err := json.Unmarshal([]byte(pro.Enum), &data)
			if err != nil {
				log.Errorf("解析JSON出错:", err)
				return err
			}
			str, err := json.Marshal(data)
			if err != nil {
				log.Errorf("转换出错:", err)
				return err
			}
			enum = string(str)
		}

		credentialCptClaim := &entity.CredentialCptClaim{
			Id:          uuid.Generate(),
			CptId:       cptId,
			Property:    pro.Property,
			Description: pro.Description,
			ValueType:   pro.Type,
			ValueEnum:   enum,
			Seq:         i + 1,
			CreatedTime: time.Now(),
			UpdatedTime: time.Now(),
			Status:      "1",
		}
		_, errs := ds.engine.InsertOne(credentialCptClaim)
		if errs != nil {
			log.Error(errs)
			return errs
		}
	}
	return nil
}

func (ds *DidService) ListCptPage(pageSize, pageNo int, title, walletId, userType, status, issuerName string) *db.Page {
	args := make([]interface{}, 0)
	condition := "1 = 1"
	if userType == constant.UserTypeIssuer {
		issuer := make([]entity.AuthorityIssuer, 0)
		issuerErr := ds.engine.Where("wallet_id = ? and status = '1'", walletId).Find(&issuer)
		if issuerErr != nil {
			return nil
		}
		if len(issuer) == 0 {
			log.Errorf("发证方信息为空!")
			return nil
		}
		issuerId := issuer[0].Id
		condition = utils.StrJoin(condition, " and credential_cpt.publisher_id = ? ")
		args = append(args, issuerId)
		condition = utils.StrJoin(condition, " and credential_cpt.status in ('0','1') ")
		args = append(args)
	}

	if userType == constant.UserTypeAdmin {
		condition = utils.StrJoin(condition, " and credential_cpt.status in ('0','1','2') ")
		args = append(args)
	}

	if userType == constant.UserTypePerson || userType == constant.UserTypeEnterprise {
		condition = utils.StrJoin(condition, " and credential_cpt.status in ('0','1') ")
		args = append(args)
	}

	if status != "" {
		condition = utils.StrJoin(condition, " and credential_cpt.status = ?")
		args = append(args, status)
	}

	if issuerName != "" {
		condition = utils.StrJoin(condition, " and authority_issuer.name like concat('%', ?::varchar, '%')")
		args = append(args, issuerName)
	}

	if title != "" {
		condition = utils.StrJoin(condition, " and credential_cpt.title like concat('%', ?::varchar, '%') ")
		args = append(args, title)
	}
	condition = utils.StrJoin(condition, " and authority_issuer.status = '1'")
	args = append(args)
	page := db.NewPage(0, pageNo).SetPageSize(pageSize)
	//var countCredentialCpt entity.CredentialCpt
	var cptIssuer CptIssuer
	count, err := ds.engine.Table("credential_cpt").
		Join("LEFT", "authority_issuer", "credential_cpt.publisher_id = authority_issuer.id").
		Where(condition, args...).Count(&cptIssuer)
	if err != nil {
		log.Errorf("query Cpt list count error: %s", err)
		return nil
	}
	page.SetTotal(count)

	//cptList := make([]*entity.CredentialCpt, 0)
	cptIssuers := make([]*CptIssuer, 0)
	err = ds.engine.Table("credential_cpt").
		Join("LEFT", "authority_issuer", "credential_cpt.publisher_id = authority_issuer.id").
		Where(condition, args...).Limit(pageSize, (pageNo-1)*pageSize).
		Asc("credential_cpt.status").Desc("credential_cpt.updated_time").Find(&cptIssuers)
	err = err
	if err != nil {
		log.Errorf("query cptList error: %s", err)
		return nil
	}
	cptListResult := make([]*model.CptListResult, 0)
	for _, cpt := range cptIssuers {
		cptListResult = append(cptListResult, &model.CptListResult{
			Id:          cpt.CredentialCpt.Id,
			Title:       cpt.CredentialCpt.Title,
			Publisher:   cpt.CredentialCpt.PublisherDid,
			CreatedTime: cpt.CredentialCpt.CreatedTime.Format(constant.TimeLayout),
			UpdatedTime: cpt.CredentialCpt.UpdatedTime.Format(constant.TimeLayout),
			CptVersion:  cpt.CredentialCpt.CptVersion,
			Status:      cpt.CredentialCpt.Status,
			IssuerName:  cpt.AuthorityIssuer.Name,
		})
	}
	page.CreatePage(cptListResult)
	return page
}

func (ds *DidService) GetCptEnum(publisherId string) (*model.CptEnumListResult, error) {
	cpt := make([]entity.CredentialCpt, 0)
	cptEnumResult := &model.CptEnumListResult{}
	cptErr := ds.engine.Where("publisher_id = ? and status = '1'", publisherId).Find(&cpt)
	if cptErr != nil {
		return cptEnumResult, cptErr
	}
	if len(cpt) == 0 {
		return cptEnumResult, errors.New("CPT 信息为空!")
	}

	enumResult := cptEnumResult.CptEnumResult
	for _, credentialCpt := range cpt {
		enumResult = append(enumResult, model.CptEnumResult{
			Title: credentialCpt.Title,
			Id:    credentialCpt.Id,
		})
	}
	cptEnumResult.CptEnumResult = enumResult
	return cptEnumResult, nil
}
func (ds *DidService) ListCptProperty(cptId string) (*model.CptClaimPropertyResult, error) {
	credentialCptClaim := make([]entity.CredentialCptClaim, 0)
	CptClaimPropertyResult := &model.CptClaimPropertyResult{}
	cptErr := ds.engine.Where("cpt_id = ? and status = '1'", cptId).Find(&credentialCptClaim)
	if cptErr != nil {
		return CptClaimPropertyResult, cptErr
	}
	if len(credentialCptClaim) == 0 {
		return CptClaimPropertyResult, errors.New("CPTClaim is Empty!")
	}

	properties := CptClaimPropertyResult.ClaimProperties
	for _, cpt := range credentialCptClaim {
		properties = append(properties, model.ClaimProperties{
			Property: cpt.Property,
			Type:     cpt.ValueType,
		})

	}
	CptClaimPropertyResult.ClaimProperties = properties
	return CptClaimPropertyResult, nil
}

func (ds *DidService) GetIssuerInfo(cptId, walletId string) (*model.IssuerResult, error) {

	cpt := make([]entity.CredentialCpt, 0)
	cptErr := ds.engine.Where("id = ?", cptId).Find(&cpt)
	if cptErr != nil {
		return &model.IssuerResult{}, cptErr
	}
	if len(cpt) == 0 {
		return &model.IssuerResult{}, errors.New("CPT信息为空!")
	}
	cptIssuerId := cpt[0].PublisherId

	issuer := make([]entity.AuthorityIssuer, 0)
	issuerErr := ds.engine.Where("wallet_id = ? and status = '1'", walletId).Find(&issuer)
	if issuerErr != nil {
		return &model.IssuerResult{}, issuerErr
	}
	if len(issuer) == 0 {
		return &model.IssuerResult{}, errors.New("发证方信息信息为空!")
	}
	issuerId := issuer[0].Id

	issuerResult := &model.IssuerResult{
		CptIssuerId: cptIssuerId,
		IssuerId:    issuerId,
	}

	return issuerResult, nil
}

func (ds *DidService) GetSignature(cptParam *param.DidCPTParam, marshal []byte) (entity.AuthorityIssuer, string, error) {
	var issuer entity.AuthorityIssuer
	ok, err := ds.engine.Where("wallet_id = ? and status = '1'", cptParam.WalletId).Get(&issuer)
	if err != nil {
		log.Errorf("get issuer error: %v", err)
		return entity.AuthorityIssuer{}, "", err
	}
	if !ok {
		return entity.AuthorityIssuer{}, "", errors.New("发证方不存在!")
	}
	keyId := issuer.Did + constant.DefaultKey1Subfix
	var docKey entity.DidDocumentKey
	ok, err = ds.engine.Where("key_id = ?", keyId).Get(&docKey)
	if err != nil {
		log.Errorf("get document key error: %v", err)
		return entity.AuthorityIssuer{}, "", err
	}
	if !ok {
		return entity.AuthorityIssuer{}, "", errors.New("秘钥不存在!")
	}
	prvKey := docKey.PrvKey
	sign := gm.Sign(prvKey, marshal)
	if sign == "" {
		return entity.AuthorityIssuer{}, "", errors.New("签名失败!")
	}
	return issuer, sign, nil
}

func (ds *DidService) EditStatus(cptId, status string) error {
	credentialCpt := &entity.CredentialCpt{
		Status: status,
	}
	_, err := ds.engine.Update(credentialCpt, &entity.CredentialCpt{Id: cptId})
	if err != nil {
		log.Error(err)
		return err
	}
	return nil
}

func (ds *DidService) Uplink(signature entity.AuthorityIssuer, credentialCpt *entity.CredentialCpt) error {
	cptClaims := make([]entity.CredentialCptClaim, 0)
	cptClaimsErr := ds.engine.Where("cpt_id = ? and status = '1'", credentialCpt.Id).Find(&cptClaims)
	if cptClaimsErr != nil {
		log.Errorf("get cptClaims error: %v", cptClaimsErr)
		return nil
	}
	if len(cptClaims) == 0 {
		return nil
	}

	properties := model2.CptInfo{}.Claims
	for _, claim := range cptClaims {
		propertys := model2.ClaimInfo{}
		propertys.ValueType = claim.ValueType
		propertys.Description = claim.Description
		propertys.Property = claim.Property
		properties = append(properties, propertys)
	}

	info := model2.CptInfo{
		Id:        credentialCpt.Id,
		Title:     credentialCpt.Title,
		IssuerId:  signature.Id,
		IssuerDid: signature.Did,
		Claims:    properties,
	}

	// 上链
	go func(id string, cptInfo *model2.CptInfo) {
		txId, _ := ds.getChainClient().SaveCptInfo(cptInfo)
		credentialCpts := entity.CredentialCpt{
			TxId: txId,
		}
		_, linkErr := ds.engine.Where("id = ?", id).Cols("tx_id").Update(&credentialCpts)
		if linkErr != nil {
			log.Errorf("updata cpt txid error: %v", linkErr)
		}
	}(credentialCpt.Id, &info)

	return nil
}
